package com.ruoyi.framework.config.properties;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.ruoyi.common.annotation.Anonymous;

/**
 * 设置Anonymous注解允许匿名访问的url
 * 
 * @author ruoyi
 */
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
    private static final Logger log = LoggerFactory.getLogger(PermitAllUrlProperties.class);

    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

    private ApplicationContext applicationContext;

    private List<String> urls = new ArrayList<>();

    @Value("${security.anonymous.urls:}")
    private List<String> anonymousUrls;

    @Override
    public void afterPropertiesSet()
    {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        mapping.getHandlerMethods().forEach((info, method) -> {
            Anonymous methodAnnotation = AnnotationUtils.findAnnotation(method.getMethod(), Anonymous.class);
            Anonymous classAnnotation = AnnotationUtils.findAnnotation(method.getBeanType(), Anonymous.class);
            if (methodAnnotation != null || classAnnotation != null)
            {
                if (info.getPatternsCondition() != null)
                {
                    info.getPatternsCondition().getPatterns().forEach(url -> {
                        try {
                            String sanitizedUrl = sanitizeUrl(url);
                            if (sanitizedUrl != null && !urls.contains(sanitizedUrl))
                            {
                                log.info("处理Anonymous注解URL: {} -> {}", url, sanitizedUrl);
                                urls.add(sanitizedUrl);
                            }
                        } catch (Exception e) {
                            log.error("处理URL时出错: {}, 错误信息: {}", url, e.getMessage());
                        }
                    });
                }
            }
        });

        // 处理配置文件中的匿名URL
        if (anonymousUrls != null && !anonymousUrls.isEmpty())
        {
            log.info("配置文件中的匿名URL数量: {}", anonymousUrls.size());
            
            for (String url : anonymousUrls)
            {
                if (url != null && !url.trim().isEmpty())
                {
                    try {
                        String sanitizedUrl = sanitizeUrl(url.trim());
                        if (sanitizedUrl != null && !urls.contains(sanitizedUrl))
                        {
                            log.info("配置文件匿名URL: {} -> {}", url, sanitizedUrl);
                            urls.add(sanitizedUrl);
                        }
                    } catch (Exception e) {
                        log.error("处理配置文件URL时出错: {}, 错误信息: {}", url, e.getMessage());
                    }
                }
            }
        }

        // 最后验证所有URL
        List<String> validUrls = new ArrayList<>();
        for (String url : urls) {
            // 再次验证URL是否符合Spring Security 6.x要求
            if (url.contains("**") && !url.endsWith("/**")) {
                log.warn("过滤掉不合法的URL模式: {}", url);
                continue;
            }
            validUrls.add(url);
        }
        
        // 更新URLs列表
        urls = validUrls;
        
        log.info("最终添加的匿名URL数量: {}", urls.size());
        if (!urls.isEmpty()) {
            log.debug("匿名URL列表: {}", String.join(", ", urls));
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException
    {
        this.applicationContext = context;
    }

    public List<String> getUrls()
    {
        return urls;
    }

    public void setUrls(List<String> urls)
    {
        this.urls = urls;
    }

    /**
     * 清理URL，确保符合Spring Security 6.x的要求
     * 
     * @param url 原始URL
     * @return 清理后的URL，如果有问题返回null
     */
    private String sanitizeUrl(String url)
    {
        if (url == null || url.isEmpty())
        {
            return null;
        }

        try
        {
            // 1. 处理路径变量 {xxx}，替换为*
            String sanitized = PATTERN.matcher(url).replaceAll("*");

            // 2. 去除URL中的参数部分
            if (sanitized.contains("?"))
            {
                sanitized = sanitized.substring(0, sanitized.indexOf("?"));
            }

            // 3. 处理多个连续的斜杠
            sanitized = sanitized.replaceAll("/+", "/");

            // 4. 处理URL中的双星号 (在Spring Security 6.x中，** 只能出现在末尾)
            if (sanitized.contains("**"))
            {
                // 如果双星号不在末尾，我们需要重构URL
                if (!sanitized.endsWith("/**"))
                {
                    // 如果包含中间的双星号，最简单的解决方案是转换为只有末尾有双星号的格式
                    // 找到双星号最后出现的位置
                    int lastIndex = sanitized.lastIndexOf("**");
                    
                    // 如果双星号不在末尾，则截取到双星号之前的部分，并在末尾添加/**
                    if (lastIndex != -1 && lastIndex != sanitized.length() - 2)
                    {
                        // 找到双星号前的最后一个斜杠
                        int lastSlashBeforeStar = sanitized.substring(0, lastIndex).lastIndexOf("/");
                        if (lastSlashBeforeStar != -1)
                        {
                            sanitized = sanitized.substring(0, lastSlashBeforeStar) + "/**";
                        }
                        else
                        {
                            sanitized = "/**";
                        }
                    }
                    else if (sanitized.contains("**"))
                    {
                        // 其他情况下，转换为更保守的匹配方式
                        sanitized = "/**";
                    }
                }
            }

            // 5. 处理单星号 - 在Spring Security 6中，*不能在段中间
            if (sanitized.contains("*") && !sanitized.contains("**"))
            {
                String[] segments = sanitized.split("/");
                StringBuilder sb = new StringBuilder();
                
                for (int i = 0; i < segments.length; i++)
                {
                    String segment = segments[i];
                    
                    if (segment.isEmpty())
                    {
                        continue;
                    }
                    
                    // 如果段中包含*，但不是整个段都是*
                    if (segment.contains("*") && !segment.equals("*"))
                    {
                        // 替换为单个*
                        sb.append("/*");
                    }
                    else
                    {
                        sb.append("/").append(segment);
                    }
                }
                
                sanitized = sb.length() > 0 ? sb.toString() : "/";
            }

            // 确保路径以/开头
            if (!sanitized.startsWith("/"))
            {
                sanitized = "/" + sanitized;
            }
            
            // 最后验证 - 确保没有在非末尾位置的**
            if (sanitized.contains("**") && !sanitized.endsWith("/**"))
            {
                log.warn("清理后仍有问题的URL: {}, 原始URL: {}", sanitized, url);
                return "/**"; // 最保守的做法是用全通配符替代
            }

            log.debug("URL清理: {} -> {}", url, sanitized);
            return sanitized;
        }
        catch (Exception e)
        {
            log.error("清理URL时出错: {}, 错误信息: {}", url, e.getMessage());
            return null;
        }
    }
    
    /**
     * 提供一个公共方法来清理URL，确保符合Spring Security 6.x的要求
     * 
     * @param url 原始URL
     * @return 清理后的URL，如果有问题返回null
     */
    public String sanitizeUrlForSecurity(String url)
    {
        return sanitizeUrl(url);
    }
}
